package de.pyranja.money.core;

import java.math.BigDecimal;
import java.util.Currency;

import de.pyranja.money.exception.CurrencyMismatchException;

import static com.google.common.base.Preconditions.*;

/**
 * Base value object for an amount of money in a certain currency. This class
 * offers access to the stored value and informations about it. Instances of
 * money are immutable.
 * 
 * @author Chris Borckholder
 */
public class BaseMoney implements Comparable<BaseMoney> {

	/*
	 * Internally the amount is stored as a BigDecimal to utilize its enhanced
	 * precision. The amount is wired up with a certain currency.
	 */
	protected final BigDecimal amount;
	protected final Currency currency;
	
	/**
	 * Creates an instance of BaseMoney with specified amount and currency.
	 * @param amount of money represented
	 * @param currency currency of represented money
	 */
	public BaseMoney(BigDecimal amount, Currency currency) {
		this.amount = checkNotNull(amount, "Amount can't be null");
		this.currency = checkNotNull(currency, "Currency can't be null");
	}

	// GETTER ===========================================================>
	/**
	 * @return the amount
	 */
	public BigDecimal getAmount() {
		return amount;
	}

	/**
	 * @return the currency
	 */
	public Currency getCurrency() {
		return currency;
	}

	// ATTRIBUTES ===========================================================>
	/**
	 * Determines whether this instance represents 0 money.
	 * 
	 * @return true if amount is zero
	 */
	public boolean isZero() {
		return amount.compareTo(BigDecimal.ZERO) == 0;
	}

	/**
	 * Determines whether this instance represents an amount of money greater
	 * than 0.
	 * 
	 * @return true if amount of money is greater than zero
	 */
	public boolean isPositive() {
		return amount.compareTo(BigDecimal.ZERO) > 0;
	}

	/**
	 * Determines whether this instance represents an amount of money less than
	 * 0.
	 * 
	 * @return true if amount of money is less than zero
	 */
	public boolean isNegative() {
		return amount.compareTo(BigDecimal.ZERO) < 0;
	}

	// COMPARISON ===========================================================>
	/**
	 * Compares this instance to specified instance of Money ordered by the 
	 * represented amount of money. 
	 * 
	 * @throws CurrencyMismatchException if currency of that differs from this
	 * @param that another amount of money.
	 * @return int positive int if this > that, negative if this < that, else 
	 * zero.
	 */
	public int compareTo(BaseMoney that) {
		// identity shortcut
		if (this == that) {
			return 0;
		}
		// validity
		checkNotNull(that);
		if (!this.currency.equals(that.currency)) {
			throw new CurrencyMismatchException(
					"Cannot compare money of different currencies",
					this.currency, that.currency);
		}
		// compare amounts
		return this.amount.compareTo(that.amount);
	}

	// MISC UTILS ===========================================================>
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "BaseMoney [amount=" + amount + ", currency=" + currency + "]";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((amount == null) ? 0 : amount.hashCode());
		result = prime * result
				+ ((currency == null) ? 0 : currency.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BaseMoney other = (BaseMoney) obj;
		if (amount == null) {
			if (other.amount != null)
				return false;
		} else if (amount.compareTo(other.amount) != 0)
			return false;
		if (currency == null) {
			if (other.currency != null)
				return false;
		} else if (!currency.equals(other.currency))
			return false;
		return true;
	}
}
